/*
 * This file is part of the Try! Engine project.
 *
 * Copyright 2010-2011 Emanuele Bertoldi. All rights reserved.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 * You should have received a copy of the modified BSD License along with this
 * program. If not, see <http://www.opensource.org/licenses/bsd-license.php>
 */

#include "audiomanager.h"

using namespace Try;

namespace Try
{
    class BufferData
    {
    public:
        Try::BufferId id;
        Try::String filename;
        unsigned int ref_count;

    public:
        BufferData(Try::String filename)
            :   id(0),
                filename(filename),
                ref_count(0)
        {
            id = alutCreateBufferFromFile(filename.c_str());
        }

        ~BufferData()
        {
            alDeleteBuffers(1, &id);
        }
    };

    class SourceData
    {
    public:
        SourceId id;
        BufferData* buffer;

    public:
        SourceData(BufferData* buffer)
            :   id(0),
                buffer(buffer)
        {
            alGenSources(1, &id);
            alSourcei(id, AL_BUFFER, buffer->id);
            buffer->ref_count++;
        }

        ~SourceData()
        {
            buffer->ref_count--;
            alDeleteSources(1, &id);
        }
    };
}

AudioManager::AudioManager(float doppler_factor, float doppler_velocity)
{
    alutInit(0, 0);

    this->setDopplerFactor(doppler_factor);
    this->setDopplerVelocity(doppler_velocity);
}

AudioManager::~AudioManager()
{
    this->stopAll();

    for (int i=this->sourceCount(); i>0; i--)
        this->release(i-1);

    alutExit();
}

float AudioManager::dopplerFactor() const
{
    float factor;

    alGetListenerf(AL_DOPPLER_FACTOR, &factor);

    return factor;
}

float AudioManager::dopplerVelocity() const
{
    float velocity;

    alGetListenerf(AL_DOPPLER_VELOCITY, &velocity);

    return velocity;
}

bool AudioManager::setDopplerFactor(float factor)
{
    alGetError();

    alDopplerFactor(factor);

    return (alGetError() == AL_NO_ERROR);
}

bool AudioManager::setDopplerVelocity(float velocity)
{
    alGetError();

    alDopplerVelocity(velocity);

    return (alGetError() == AL_NO_ERROR);
}

float AudioManager::listenerGain() const
{
    float gain;

    alGetListenerf(AL_GAIN, &gain);

    return gain;
}

Vector3 AudioManager::listenerPosition() const
{
    ALfloat position[3];

    alGetListenerfv(AL_POSITION, position);

    return Vector3(position);
}

Vector3 AudioManager::listenerDirection() const
{
    ALfloat direction[3];

    alGetListenerfv(AL_DIRECTION, direction);

    return Vector3(direction);
}

Vector3 AudioManager::listenerVelocity() const
{
    ALfloat velocity[3];

    alGetListenerfv(AL_VELOCITY, velocity);

    return Vector3(velocity);
}

bool AudioManager::setListenerGain(float gain)
{
    alGetError();

    alListenerf(AL_GAIN, gain);

    return (alGetError() == AL_NO_ERROR);
}

bool AudioManager::setListenerPosition(const Vector3& pos)
{
    alGetError();

    ALfloat position[] = { pos.x, pos.y, pos.z };

    alListenerfv(AL_POSITION, position);

    return (alGetError() == AL_NO_ERROR);
}

bool AudioManager::setListenerDirection(const Vector3& dir)
{
    alGetError();

    ALfloat direction[] = { dir.x, dir.y, dir.z };

    alListenerfv(AL_DIRECTION, direction);

    return (alGetError() == AL_NO_ERROR);
}

bool AudioManager::setListenerVelocity(const Vector3& vel)
{
    alGetError();

    ALfloat velocity[] = { vel.x, vel.y, vel.z };

    alListenerfv(AL_VELOCITY, velocity);

    return (alGetError() == AL_NO_ERROR);
}

int AudioManager::sourceCount() const
{
    return m_sources.size();
}

bool AudioManager::isLoaded(const Try::String& filename) const
{
    String fn = String("data/") + filename;

    for (int i=0; i < m_buffers.size(); i++)
        if (m_buffers.at(i)->filename == fn)
            return true;

    return false;
}

bool AudioManager::isPlaying(SourceId source_id) const
{
    int state = 0;
    alGetSourcei(source_id, AL_SOURCE_STATE, &state);

    return (state == AL_PLAYING);
}

float AudioManager::pitch(SourceId source_id) const
{
    float pitch;

    alGetSourcef(source_id, AL_PITCH, &pitch);

    return pitch;
}

float AudioManager::gain(SourceId source_id) const
{
    float gain;

    alGetSourcef(source_id, AL_GAIN, &gain);

    return gain;
}

Vector3 AudioManager::position(SourceId source_id) const
{
    ALfloat position[3];

    alGetSourcefv(source_id, AL_POSITION, position);

    return Vector3(position);
}

Vector3 AudioManager::direction(SourceId source_id) const
{
    ALfloat direction[3];

    alGetSourcefv(source_id, AL_DIRECTION, direction);

    return Vector3(direction);
}

Vector3 AudioManager::velocity(SourceId source_id) const
{
    ALfloat velocity[3];

    alGetSourcefv(source_id, AL_VELOCITY, velocity);

    return Vector3(velocity);
}

SourceId AudioManager::load(const Try::String& filename)
{
    String fn = String("data/") + filename;

    int i = this->findBuffer(fn);

    if (i < 0)
    {
        i = m_buffers.size();
        BufferData* buffer = new BufferData(fn);
        m_buffers.push_back(buffer);
    }

    SourceData* source = new SourceData(m_buffers[i]);
    m_sources.push_back(source);

    return source->id;
}

bool AudioManager::release(SourceId source_id)
{
    for (int i=0; i < m_sources.size(); i++)
    {
        SourceData* source = m_sources.at(i);

        if (source && source->id == source_id)
        {
            BufferData* buffer = source->buffer;
            m_sources.erase(m_sources.begin()+i);
            delete source;

            if (buffer->ref_count == 0)
            {
                int i = this->findBuffer(buffer->filename);
                m_buffers.erase(m_buffers.begin()+i);
                delete buffer;
            }

            return true;
        }
    }

    return false;
}

bool AudioManager::play(SourceId source_id, bool loop, bool force_restart)
{
    if (this->isPlaying(source_id) && force_restart)
        this->stop(source_id);

    alGetError();

    alSourcei(source_id, AL_LOOPING, loop);
    alSourcePlay(source_id);

    return (alGetError() == AL_NO_ERROR);
}

bool AudioManager::pause(SourceId source_id)
{
    alGetError();

    alSourcePause(source_id);

    return (alGetError() == AL_NO_ERROR);
}

bool AudioManager::stop(SourceId source_id)
{
    alGetError();

    alSourceStop(source_id);

    return (alGetError() == AL_NO_ERROR);
}

bool AudioManager::stopAll()
{
    for (int i = 0; i < m_sources.size(); i++)
        if (!this->stop(m_sources[i]->id))
            return false;

    return true;
}

bool AudioManager::setPitch(SourceId source_id, float pitch)
{
    alGetError();

    alSourcef(source_id, AL_PITCH, pitch);

    return (alGetError() == AL_NO_ERROR);
}

bool AudioManager::setGain(SourceId source_id, float gain)
{
    alGetError();

    alSourcef(source_id, AL_GAIN, gain);

    return (alGetError() == AL_NO_ERROR);
}

bool AudioManager::setPosition(SourceId source_id, const Vector3& pos)
{
    alGetError();

    ALfloat position[] = { pos.x, pos.y, pos.z };

    alSourcefv(source_id, AL_POSITION, position);

    return (alGetError() == AL_NO_ERROR);
}

bool AudioManager::setDirection(SourceId source_id, const Vector3& dir)
{
    alGetError();

    ALfloat direction[] = { dir.x, dir.y, dir.z };

    alSourcefv(source_id, AL_DIRECTION, direction);

    return (alGetError() == AL_NO_ERROR);
}

bool AudioManager::setVelocity(SourceId source_id, const Vector3& vel)
{
    alGetError();

    ALfloat velocity[] = { vel.x, vel.y, vel.z };

    alSourcefv(source_id, AL_VELOCITY, velocity);

    return (alGetError() == AL_NO_ERROR);
}

int AudioManager::findBuffer(const Try::String& filename)
{
    for (int i = 0; i < m_buffers.size(); i++)
        if (m_buffers[i]->filename == filename)
            return i;

    return -1;
}
